use crate::{input::Input, renderer::Renderer};

pub enum UpdateStatus {
    Fine,
    Exit,
}

pub struct SceneManager<E> {
    scenes: Vec<Box<dyn Scene<E>>>,
}

impl<E> SceneManager<E> {
    pub fn new(first: Box<dyn Scene<E>>) -> Self {
        Self {
            scenes: vec![first],
        }
    }

    pub fn update(&mut self, input: &mut Input<E>) -> UpdateStatus {
        if let Some(scene) = self.scenes.last_mut() {
            return match scene.update(input) {
                SceneControl::Continue => UpdateStatus::Fine,
                SceneControl::Back => {
                    let _ = self.scenes.pop();
                    if self.scenes.is_empty() {
                        return UpdateStatus::Exit;
                    }
                    UpdateStatus::Fine
                }
                SceneControl::StackScene(mut new_scene) => {
                    scene.hide();
                    new_scene.show();
                    self.scenes.push(new_scene);
                    UpdateStatus::Fine
                }
                SceneControl::ReplaceScene(mut new_scene) => {
                    scene.hide();
                    new_scene.show();
                    let _ = self.scenes.pop();
                    self.scenes.push(new_scene);
                    UpdateStatus::Fine
                }
            }
        }
        UpdateStatus::Exit
    }

    pub fn render(&mut self, renderer: &mut dyn Renderer) {
        if let Some(scene) = self.scenes.last_mut() {
            scene.render(renderer);
        }
    }
}

pub trait Scene<E> {
    fn show(&mut self);
    fn update(&mut self, input: &mut Input<E>) -> SceneControl<E>;
    fn render(&mut self, renderer: &mut dyn Renderer);
    fn hide(&mut self);
}

pub enum SceneControl<E> {
    Continue,
    Back,
    StackScene(Box<dyn Scene<E>>),
    ReplaceScene(Box<dyn Scene<E>>)
}

#[cfg(test)]
mod test {
    use crate::{input::{Input, InputMap}, renderer::Renderer, scene::{SceneManager, Scene, SceneControl, UpdateStatus}};

    #[test]
    fn scene_switch() {
        struct FirstScene;

        struct SecondScene;

        impl Scene<InputMap> for FirstScene {
            fn show(&mut self) {
                println!("First scene show!");
            }
            
            fn update(&mut self, input: &mut Input<InputMap>) -> SceneControl<InputMap> {
                while let Some(event) = input.poll() {
                    if event == InputMap::Enter {
                        return SceneControl::ReplaceScene(Box::new(SecondScene))
                    }
                }
                SceneControl::Continue
            }

            fn render(&mut self, _renderer: &mut dyn Renderer) {
                
            }

            fn hide(&mut self) {
                println!("First scene hide!");
            }
        }

        impl Scene<InputMap> for SecondScene {
            fn show(&mut self) {
                println!("Second scene show!");
            }

            fn update(&mut self, input: &mut Input<InputMap>) -> SceneControl<InputMap> {
                while let Some(event) = input.poll() {
                    if event == InputMap::Exit {
                        println!("Exiting!");
                        return SceneControl::Back;
                    }
                }
                println!("Second update!");
                SceneControl::Continue
            }

            fn render(&mut self, _renderer: &mut dyn Renderer) {
                
            }

            fn hide(&mut self) {
                println!("Second scene hide!")
            }
        }

        let mut mgr = SceneManager::new(Box::new(FirstScene));
        let mut input = Input::new();
        
        let mut tick = 0;

        loop {
            println!("Tick: {tick}");
            
            if tick == 100 {
                input.push_event(InputMap::Enter);
            }

            if tick == 200 {
                input.push_event(InputMap::Exit);
            }

            match mgr.update(&mut input) {
                UpdateStatus::Fine => (),
                UpdateStatus::Exit => break,
            }
            tick += 1;
        }
    }
}